/*
 * Copyright (c) 2024 Black Sesame Technologies
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef _DAL_SEQUENCE_BASE_
#define _DAL_SEQUENCE_BASE_

#include <stdint.h>
#include <stddef.h>
#include <memory.h>
#include <stdlib.h>

#ifdef __cplusplus
extern "C" {
#endif

typedef struct SEQUENCE_BASE
{
    uint64_t _size;
    uint64_t _capacity;
    uint8_t* _handle;
    uint64_t _element_size;
    const char* _type_name;
} SEQUENCE_BASE;

#define DAL_DEFAULT_CAPACITY 0xff

#define SEQUENCE(Type) SEQUENCE_BASE

// must deep_clear before reinit sequence
#define INIT_SEQUENCE_WITH_CAPACITY(Type, sequence, capacity)                                      \
    {                                                                                              \
        (sequence)->_size = 0;                                                                     \
        (sequence)->_capacity = capacity;                                                          \
        (sequence)->_type_name = #Type;                                                            \
        (sequence)->_element_size = sizeof(Type);                                                  \
        (sequence)->_handle = (uint8_t*)malloc(capacity * sizeof(Type));                           \
        memset((sequence)->_handle, 0, capacity * sizeof(Type));                                   \
    }

#define INIT_SEQUENCE(Type, sequence)                                                              \
    INIT_SEQUENCE_WITH_CAPACITY(Type, sequence, DAL_DEFAULT_CAPACITY)

#define RETURN_RET ret

#define CHECK_SEQUENCE_TYPE(Type, sequence)                                                        \
    ({                                                                                             \
        int ret = -1;                                                                              \
        if (strcmp(#Type, (sequence)->_type_name) == 0) {                                          \
            if (sizeof(Type) == (sequence)->_element_size) {                                       \
                ret = 0;                                                                           \
            }                                                                                      \
        };                                                                                         \
        RETURN_RET;                                                                                \
    })

#define COPY_SEQUENCE(Type, desc, source)                                                          \
    ({                                                                                             \
        int ret = -1;                                                                              \
        if (CHECK_SEQUENCE_TYPE(Type, desc) == 0 && CHECK_SEQUENCE_TYPE(Type, source) == 0) {      \
            (desc)->_size = (source)->_size;                                                       \
            (desc)->_capacity = (source)->_capacity;                                               \
            (desc)->_element_size = (source)->_element_size;                                       \
            (desc)->_type_name = (source)->_type_name;                                             \
            free((desc)->_handle);                                                                 \
            (desc)->_handle = (uint8_t*)malloc((source)->_capacity * (source)->_element_size);     \
            memcpy((desc)->_handle, (source)->_handle, (source)->_size*(source)->_element_size);   \
            ret = 0;                                                                               \
        }                                                                                          \
        ret;                                                                                       \
    })

#define CLEAR_SEQUENCE(Type, sequence)                                                             \
    {                                                                                              \
        (sequence)->_size = 0;                                                                     \
        memset((sequence)->_handle, 0, (sequence)->_element_size*(sequence)->_capacity);           \
    }

#define DEEP_CLEAR_SEQUENCE(Type, sequence)                                                        \
    {                                                                                              \
        free((sequence)->_handle);                                                                 \
        (sequence)->_handle = NULL;                                                                \
    }

#define PUSH_BACK_SEQUENCE(Type, sequence, element)                                                \
    ({                                                                                             \
        int ret = -1;                                                                              \
        if (CHECK_SEQUENCE_TYPE(Type, sequence) == 0) {                                            \
            if ((sequence)->_size >= (sequence)->_capacity) {                                      \
                uint8_t* tmp_handle = (sequence)->_handle;                                         \
                (sequence)->_handle = (uint8_t*)malloc(                                            \
                    ((sequence)->_capacity + DAL_DEFAULT_CAPACITY) * (sequence)->_element_size);   \
                memcpy((sequence)->_handle,                                                        \
                       tmp_handle,                                                                 \
                       (sequence)->_capacity*(sequence)->_element_size);                           \
                (sequence)->_capacity += DAL_DEFAULT_CAPACITY;                                     \
            }                                                                                      \
            *(((Type*)(sequence)->_handle) + (sequence)->_size) = (element);                       \
            ++((sequence)->_size);                                                                 \
            ret = 0;                                                                               \
        }                                                                                          \
        RETURN_RET;                                                                                \
    })

#define PUSH_BACK_SEQUENCE_ARRAY(Type, sequence, elements, len)                                    \
    ({                                                                                             \
        int ret = -1;                                                                              \
        if ((sequence)->_size + (len) <= (sequence)->_capacity) {                                  \
            if (CHECK_SEQUENCE_TYPE(Type, sequence) == 0) {                                        \
                uint64_t offset_ = (sequence)->_size * (sequence)->_element_size;                  \
                memcpy(                                                                            \
                    (sequence)->_handle + offset_, (elements), (len) * (sequence)->_element_size); \
                (sequence)->_size += len;                                                          \
                ret = 0;                                                                           \
            }                                                                                      \
        };                                                                                         \
        RETURN_RET;                                                                                \
    })

#define POP_BACK_SEQUENCE(Type, sequence)                                                          \
    ({                                                                                             \
        int ret = -1;                                                                              \
        if (CHECK_SEQUENCE_TYPE(Type, sequence) == 0) {                                            \
            if ((sequence)->_size > 0) {                                                           \
                (sequence)->_size--;                                                               \
                ret = 0;                                                                           \
            }                                                                                      \
        }                                                                                          \
        RETURN_RET;                                                                                \
    })

#define SIZE_SEQUENCE(Type, sequence) (sequence)->_size

#define GET_ELEMENT_SEQUENCE(Type, sequence, index, out_element)                                   \
    ({                                                                                             \
        int ret = -1;                                                                              \
        if ((index) < (sequence)->_size && CHECK_SEQUENCE_TYPE(Type, sequence) == 0) {             \
            *(out_element) = *(((Type*)(sequence)->_handle) + (index));                            \
            ret = 0;                                                                               \
        }                                                                                          \
        RETURN_RET;                                                                                \
    })

#define SET_ELEMENT_SEQUENCE(Type, sequence, index, element)                                       \
    ({                                                                                             \
        int ret = -1;                                                                              \
        if ((index) < (sequence)->_size && CHECK_SEQUENCE_TYPE(Type, sequence) == 0) {             \
            *(((Type*)(sequence)->_handle) + index) = element;                                     \
            ret = 0;                                                                               \
        }                                                                                          \
        RETURN_RET;                                                                                \
    })

#ifdef __cplusplus
}
#endif

#endif  // _DAL_SEQUENCE_BASE_